package de.zainodis.commons.logreport;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Calendar;

import android.content.Context;
import android.os.Build;
import de.zainodis.commons.LogCat;
import de.zainodis.commons.utils.ApplicationUtils;
import de.zainodis.commons.utils.DateTimeUtils;
import de.zainodis.commons.utils.RegexUtils;
import de.zainodis.commons.utils.StringUtils;

/**
 * Generates a logreport that can be persisted to the database and could be sent
 * as soon as an internet connection is available.
 * 
 * @author fzarrai
 * 
 */
public class Report {

   private static final String TAG = "Report";

   private static final String MATCH_ERROR_LOG = ".*([E]{1}/.*).*";

   /** The actual report content */
   private String logContent;
   /** Date/Time when the report was created as ISO8601 time string */
   private String dateTimeString;

   private Report() {
   }

   public static Report writeReport(String targetTag) throws IOException {
	 /*
	  * Creates a logfile and formats it using timestamps (-v time). The log
	  * level is set to Info for our application's tag. The (*:S silences all
	  * other log statements, to ensure that only the logs that pass our filter
	  * get extracted.)
	  */
	 Report report = new Report();
	 // Set the current time as the log creation time
	 report.dateTimeString = DateTimeUtils.format(Calendar.getInstance(),
		  DateTimeUtils.DATETIME_FORMAT_ISO8601);

	 String command = "logcat -d -v time *:I";
	 Process process = Runtime.getRuntime().exec(command);

	 BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
		  process.getInputStream()));

	 StringBuilder log = new StringBuilder();
	 String line;
	 while ((line = bufferedReader.readLine()) != null) {
	    if (line != null && (isApplicationLog(line, targetTag) || isErrorLog(line))) {
		  // Only append the line if it contains the given tag name
		  log.append(line);
		  log.append(StringUtils.NEW_LINE);
	    }
	 }

	 // Set the log as the reports main message
	 report.logContent = log.toString();

	 return report;
   }

   /**
    * The application log tag is determined by calling
    * {@link LogCat#getApplicationTag()}.
    * 
    * @param line
    *           the log entry to evaluate.
    * @param targetTag
    *           the target tag to filter by.
    * @return if the given log entry has been logged by this application; false
    *         otherwise.
    */
   private static boolean isApplicationLog(String line, String targetTag) {
	 return RegexUtils.matchesExactly(
		  String.format(".*([IWE]{1}/%s).*", LogCat.getApplicationTag()), line);
   }

   /**
    * @param line
    *           the log entry to evaluate.
    * @return if the given log entry is an error log; false otherwise.
    */
   private static boolean isErrorLog(String line) {
	 return RegexUtils.matchesExactly(MATCH_ERROR_LOG, line);
   }

   /**
    * Formats the given {@link Report} by adding additional information and
    * putting it into a readable shape.
    * 
    * @param report
    *           the report to format.
    * @param context
    *           the Context that is formatting this report.
    * @return a formatted report that can be easily read.
    */
   public String formatReport(Report report, Context context) {
	 StringBuilder log = new StringBuilder();
	 // Writes basic application details
	 writeReportDetails(log, report);
	 // Writes the application version
	 writeApplicationVersion(log, context);
	 // Writes system information to the log
	 writeSystemInformation(log);
	 // Appends the actual logfile
	 writeLogExtract(log, report);

	 return log.toString();

   }

   protected void writeReportDetails(StringBuilder log, Report report) {
	 log.append("------ Generated Report ------");
	 log.append(StringUtils.NEW_LINE);
	 log.append(StringUtils.NEW_LINE);
	 log.append(String.format("Logfile created at: %s", report.dateTimeString));
	 log.append(StringUtils.NEW_LINE);
   }

   /**
    * Writes the application version. If the version can't be determined, "?" is
    * written instead.
    * 
    * @param log
    *           the {@link StringBuilder} to write to.
    */
   protected void writeApplicationVersion(StringBuilder log, Context context) {
	 log.append(String.format("Application Version: %s",
		  ApplicationUtils.getApplicationVersion(context)));
	 log.append(StringUtils.NEW_LINE);
	 log.append(StringUtils.NEW_LINE);

   }

   /**
    * Writes the log extract from the given {@link Report} to the given
    * {@link StringBuilder}.
    * 
    * @param log
    *           the {@link StringBuilder} to write to.
    * @param report
    *           the report from which the log is extracted.
    */
   protected void writeLogExtract(StringBuilder log, Report report) {
	 log.append("------ BEGINNING OF LOGFILE ------");
	 log.append(StringUtils.NEW_LINE);
	 log.append(StringUtils.NEW_LINE);
	 // Append actual log file message
	 log.append(report.logContent);
   }

   /**
    * Writes general system information into the log, such as the device id,
    * maximum memory etc..
    * 
    * @param log
    *           the {@link StringBuilder} to write to.
    */
   protected void writeSystemInformation(StringBuilder log) {
	 // General system information for the logfile header
	 log.append(String.format("Android Release Version: %s", Build.VERSION.RELEASE));
	 log.append(StringUtils.NEW_LINE);
	 log.append(String.format("Available Processors: %s", Runtime.getRuntime()
		  .availableProcessors()));
	 log.append(StringUtils.NEW_LINE);
	 log.append(String.format("Available Memory for Application (bytes): %s", Runtime.getRuntime()
		  .totalMemory()));
	 log.append(StringUtils.NEW_LINE);
	 log.append(StringUtils.NEW_LINE);
   }

}
